package org.hawk.gwt.ppc.reflect;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hawk.gwt.ppc.javacc.JavaParserTreeConstants;
import org.hawk.gwt.ppc.reflect.lang.LangClassLoader;
import org.hawk.gwt.ppc.reflect.lang.SystemClassLoader;
import org.hawk.gwt.ppc.scanner.Processor;
import org.hawk.gwt.ppc.scanner.Token;
import org.hawk.gwt.ppc.scanner.TokenChain;
import org.hawk.gwt.ppc.scanner.utils.JavaClassDescriptor;
import org.hawk.gwt.ppc.utils.Name;
/**
 * Collects all the metadata for classes and links it to the entry point of
 * the GWT module. If module has more than one entry point - all classes will be 
 * linked to the first one.
 * @author alex.bereznevatiy@gmail.com
 * @deprecated since 0.2 - almost all logic should be replaced with 
 * {@link JavaClassDescriptor}. Left only logic of class metadata writing.
 */
final class MetadataProcessor implements Processor {
	private static final String IMPORT_KEYWORD = "import";
	private static final String IMPLEMENTS_KEYWORD = "implements";
	private static final String THIS_KEYWORD = "this";
	private static final String SUPER_KEYWORD = "super";
	private static final Object INTERFACE_KEYWORD = "interface";
	
	private static final int DEFAULT_STATE = 0;
	private static final int ALREADY_GOT_MAIN_CLASS = 1;
	private static final int ANONIMOUS_CLASS = 2;

	static final int TYPE_CLASS = 1;
	static final int TYPE_FIELD = 2;
	static final int TYPE_CONSTRUCTOR = 4;
	static final int TYPE_METHOD = 8;
	
	private int state;
	private LinkedList<String> classes;
	private Set<String> interfaces;
	private Set<String> innerClasses;
	private String currentPackage;
	private int currentPackageStart;
	private String currentClass;
	private Map<String,Map<String,List<JavaModifier>>> modifiers;
	private Map<String,List<String>> fields;
	private Map<String,List<String>> constructors;
	private Map<String,List<String>> methods;
	private Map<String,String> superClasses;
	private Map<String,List<String>> implementedInterfaces;
	private Map<String,List<String>> imports;
	private Map<String,String> importsCache;
	/**
	 * Creates processor for package-internal use.
	 */
	MetadataProcessor(){
		clear();
	}
	/**
	 * Clears all data for the current MetadataProcessor.
	 * Processor is still able to proceed with processing java sources but
	 * all previously stored metadata will become unavailable.
	 */
	void clear() {
		state = DEFAULT_STATE;
		classes = new LinkedList<String>();
		fields = new HashMap<String, List<String>>();
		constructors = new HashMap<String, List<String>>();
		methods = new HashMap<String, List<String>>();
		implementedInterfaces = new HashMap<String, List<String>>();
		superClasses = new HashMap<String,String>();
		modifiers = new HashMap<String,Map<String,List<JavaModifier>>>();
		imports = new HashMap<String,List<String>>();
		importsCache = new HashMap<String, String>();
		interfaces = new HashSet<String>();
		innerClasses = new HashSet<String>();
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.Processor#visitStart(org.hawk.gwt.ppc.scanner.TokenChain, int)
	 */
	public void visitStart(TokenChain<? extends Token> iterator,int kind) {
		if(state==ANONIMOUS_CLASS)return;
		if(kind==JavaParserTreeConstants.JJTCOMPILATIONUNIT){
			state = DEFAULT_STATE;
			currentPackage = "";
			currentClass = null;
		}
		else if(kind==JavaParserTreeConstants.JJTPACKAGEDECLARATION){
			currentPackageStart = iterator.current().getIndex();
		}
		else if(kind==JavaParserTreeConstants.JJTCLASSORINTERFACEBODY){
			addClassIfNotGot(iterator);
			if(state==ALREADY_GOT_MAIN_CLASS)state = DEFAULT_STATE;
		}
		else if(kind==JavaParserTreeConstants.JJTEXTENDSLIST){
			addClassIfNotGot(iterator);
			if(state==DEFAULT_STATE)state = ALREADY_GOT_MAIN_CLASS;
		}
		else if(kind==JavaParserTreeConstants.JJTIMPLEMENTSLIST){
			addClassIfNotGot(iterator);
			if(state==DEFAULT_STATE)state = ALREADY_GOT_MAIN_CLASS;
		}
		else if(kind==JavaParserTreeConstants.JJTFIELDDECLARATION){
			addField(iterator);
		}
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.Processor#visitEnd(org.hawk.gwt.ppc.scanner.TokenChain, int)
	 */
	public void visitEnd(TokenChain<? extends Token> iterator,int kind){
		if(kind==JavaParserTreeConstants.JJTCLASSORINTERFACEBODY){
			state = DEFAULT_STATE;
		}
		if(state==ANONIMOUS_CLASS)return;
		if(kind==JavaParserTreeConstants.JJTCOMPILATIONUNIT){
			state = DEFAULT_STATE;
			importsCache.clear();
			ClassDelegateBuilder.build(currentClass,this)
				.write(iterator.current());
		}
		else if(kind==JavaParserTreeConstants.JJTPACKAGEDECLARATION){
			resolvePackageName(iterator);
		}
		else if(kind==JavaParserTreeConstants.JJTEXTENDSLIST){
			addExtendedClass(iterator);
		}
		else if(kind==JavaParserTreeConstants.JJTIMPLEMENTSLIST){
			addImplemenedInterfaces(iterator);
		}
		else if(kind==JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION){
			addConstructor(iterator);
		}
		else if(kind==JavaParserTreeConstants.JJTMETHODDECLARATOR){
			addMethod(iterator);
		}
		else if(kind==JavaParserTreeConstants.JJTIMPORTDECLARATION){
			addImportDeclaration(iterator);
		}
	}
	/**
	 * @return list of all the collected classes.
	 */
	public List<String> getClasses() {
		return Collections.unmodifiableList(classes);
	}
	/**
	 * @return list of all collected top-level classes.
	 */
	public List<String> getTopClasses() {
		ArrayList<String> result = new ArrayList<String>(classes.size());
		for(String name : classes){
			if(!innerClasses.contains(name)){
				result.add(name);
			}
		}
		result.trimToSize();
		return Collections.unmodifiableList(result);
	}
	/**
	 * @param className
	 * @return list of all declared fields 
	 * for the specific class with name passed.
	 */
	List<String> getDeclaredFields(String className) {
		List<String> result = fields.get(className);
		if(result==null){
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(result);
	}
	/**
	 * @param className
	 * @return list of all declared constructors
	 * for the specific class with name passed.
	 * Default constructor is not returned.
	 */
	List<String> getDeclaredConstructors(String className) {
		List<String> result = constructors.get(className);
		if(result==null){
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(result);
	}
	/**
	 * @param className
	 * @return list of all declared methods 
	 * for the specific class with name passed.
	 */
	List<String> getDeclaredMethods(String className) {
		List<String> result = methods.get(className);
		if(result==null){
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(result);
	}
	/**
	 * @param className
	 * @return list of all implemented interfaces 
	 * for the specific class with name passed.
	 */
	List<String> getImplementedInterfaces(String className) {
		List<String> result = implementedInterfaces.get(className);
		if(result==null){
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(result);
	}
	/**
	 * @param className
	 * @return full name of the superclass
	 * for the specific class with name passed.
	 * Implicit inheritance (java.lang.Object) will
	 * be returned if there is no superclass specified explicitly.
	 */
	String getExtendedClass(String className) {
		String result = superClasses.get(className);
		if(result==null){
			return Object.class.getName();
		}
		return result;
	}
	/**
	 * Service method returns modifier for the entry (class,
	 * method, constructor,field) with signature passed. 
	 * @param className
	 * @param signature
	 * @return list of JavaModifier modifiers for the entry with signature specified
	 */
	List<JavaModifier> getModifier(String className, String signature) {
		Map<String,List<JavaModifier>> mod = modifiers.get(className);
		if(mod==null){
			return null;
		}
		return mod.get(signature);
	}
	/**
	 * @param name
	 * @return list of all imported entries for the class with name
	 * specified.
	 */
	List<String> getImports(String className) {
		List<String> result = imports.get(className);
		if(result==null){
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(result);
	}
	/**
	 * @param className
	 * @return true if the class with name passes is 
	 * processed by this processor and is an interface.
	 */
	boolean isInterface(String className) {
		return className==null?false:interfaces.contains(className);
	}
	
	private void resolvePackageName(TokenChain<? extends Token> iterator) {
		StringBuilder pkg = new StringBuilder();
		while(iterator.previous().getIndex()>
			currentPackageStart);
		while(!iterator.next().getToken().equals(";")){
			pkg.append(iterator.current().getToken());
		}
		currentPackage = pkg.toString();
	}

	private void addClassIfNotGot(TokenChain<? extends Token> iterator) {
		if(state!=ALREADY_GOT_MAIN_CLASS){
			String declaration = iterator.current().getToken();
			if(declaration.equals(")")){
				state = ANONIMOUS_CLASS;
				return;
			}
			boolean isInterface = iterator.previous()
					.getToken().equals(INTERFACE_KEYWORD);
			iterator.next();
			addClass(declaration,iterator,isInterface);
		}
	}

	private void addClass(String declaration,
			TokenChain<? extends Token> iterator,
			boolean isInterface) {
		classes.add(resolveFullName(declaration,iterator));
		if(isInterface){
			interfaces.add(classes.getLast());
		}
	}

	private String resolveFullName(String declaration,
			TokenChain<? extends Token> iterator) {
		if(currentClass!=null){
			String name = Name.getChildJavaName(currentClass,declaration);
			resolveClassModifiers(name,iterator);
			innerClasses.add(name);
			return name;
		}
		currentClass = Name.getChildJavaName(currentPackage,declaration);
		resolveClassModifiers(currentClass,iterator);
		if(imports.get(currentClass)==null){
			imports.put(currentClass, new LinkedList<String>());
		}
		
		imports.get(currentClass).addAll(importsCache.values());
		return currentClass;
	}
	
	private void resolveClassModifiers(String name, 
			TokenChain<? extends Token> iterator) {
		if(modifiers.get(name)==null){
			modifiers.put(name, new HashMap<String, List<JavaModifier>>());
		}
		iterator.previous();
		modifiers.get(name).put("", resolveModifier(iterator,TYPE_CLASS));
	}
	private List<JavaModifier> resolveModifier(
			TokenChain<? extends Token> iterator,
			int type) {
		ArrayList<JavaModifier> modifiers = new ArrayList<JavaModifier>();
		JavaModifier modifier = null;
		while((modifier = JavaModifier.resolve(
				iterator.previous().getToken()))!=null){
			modifiers.add(modifier);
		}
		JavaModifier.checkForDefaultVisibility(modifiers,
			isInterface(classes.isEmpty()?null:classes.getLast()),type);
		Collections.reverse(modifiers);
		modifiers.trimToSize();
		return Collections.unmodifiableList(modifiers);
	}
	
	private void addField(TokenChain<? extends Token> iterator) {
		String name = iterator.current().getToken();
		String type = extractType(iterator);
		putField(name, type);
		resolveModifiers(iterator, name,TYPE_FIELD);
	}
	private void putField(String name, String type) {
		if(fields.get(classes.getLast())==null){
			fields.put(classes.getLast(), new LinkedList<String>());
		}
		fields.get(classes.getLast()).add(type+' '+name);
	}
	
	private String extractType(TokenChain<? extends Token> iterator) {
		String type = getTypeFromIterator(iterator);
		if(type.indexOf('.')>=0){
			return type;
		}
		
		if(type.indexOf('[')>0){
			return extractArrayType(type);
		}
		
		return getFullTypeNameFromSimple(type);
	}
	private String extractArrayType(String type) {
		StringBuilder arrayParams = null;
		StringBuilder raw = new StringBuilder();
		raw.append(type.charAt(0));
		for(int i=1;i<type.length();i++){
			char c = type.charAt(i);
			if(arrayParams==null&&
				!Character.isJavaIdentifierPart(c)){
				arrayParams = new StringBuilder();
			}
			if(arrayParams!=null){
				arrayParams.append(c);
				continue;
			}
			raw.append(c);
		}
		arrayParams.insert(0, getFullTypeNameFromSimple(raw.toString()));
		return arrayParams.toString();
	}
	private String getFullTypeNameFromSimple(String type) {
		if(importsCache.get(type)!=null){
			return importsCache.get(type);
		}
		if(LangClassLoader.isLangClass(type)){
			return Name.getChildJavaName(LangClassLoader.LANG_PACKAGE, type);
		}
		if(SystemClassLoader.isPrimitiveClass(type)){
			return type;
		}
		return Name.getChildJavaName(currentPackage, type);
	}
	private String getTypeFromIterator(TokenChain<? extends Token> iterator) {
		int deep = 0;
		do{
			iterator.previous();
			if(iterator.current().getToken().equals(">")){
				deep++;
			}
			else if(iterator.current().getToken().equals("<")){
				deep--;
				iterator.previous();
			}
		}
		while(iterator.hasPrevious()&&deep>0);
		int index = iterator.index();
		while(!Name.isJavaName(iterator.current().getToken())){
			iterator.previous();
		}
		int moveTo = iterator.index();
		iterator.previous();
		StringBuilder result = new StringBuilder();
		while(iterator.index()<index){
			result.append(iterator.next().getToken());
		}
		iterator.moveTo(moveTo);
		return result.toString();
	}
	
	private void addConstructor(TokenChain<? extends Token> iterator){
		skipUnnecessaryData(iterator);
		StringBuilder args = getArguments(iterator);
		if(constructors.get(classes.getLast())==null){
			constructors.put(classes.getLast(), new LinkedList<String>());
		}
		args.insert(0,iterator.previous().getToken());
		String signature = args.toString();
		constructors.get(classes.getLast()).add(signature);
		resolveModifiers(iterator,signature,TYPE_CONSTRUCTOR);
	}
	
	private void skipUnnecessaryData(TokenChain<? extends Token> iterator) {
		int bracket = 1;
		while(bracket>0){
			if(iterator.current().getToken().equals("}")){
				bracket++;
			}
			else if(iterator.current().getToken().equals("{")){
				bracket--;
			}
			iterator.previous();
		}
		skipThrowsSection(iterator);
		int index = iterator.current().getIndex();
		int deep = 1;
		while(deep>0){
			if(iterator.previous().getToken().equals(")")){
				deep++;
			}
			else if(iterator.current().getToken().equals("(")){
				deep--;
			}
		}
		if(!iterator.previous().getToken().equals(SUPER_KEYWORD)&&
			!iterator.current().getToken().equals(THIS_KEYWORD)){
			while(iterator.next().getIndex()<index);
			return;
		}
		if(iterator.previous().getToken().equals("{")){
			iterator.previous();
		}
		skipThrowsSection(iterator);
	}
	
	private void skipThrowsSection(TokenChain<? extends Token> iterator) {
		while(!iterator.current().getToken().equals(")")){
			iterator.previous();
		}
	}
	private void resolveModifiers(TokenChain<? extends Token> iterator,
			String signature,int type) {
		List<JavaModifier> mod = resolveModifier(iterator,type);
		modifiers.get(classes.getLast()).put(signature, mod);
	}
	
	private StringBuilder getArguments(TokenChain<? extends Token> iterator) {
		StringBuilder builder = new StringBuilder(50);
		while(!iterator.previous().getToken().equals("(")){
			addArgument(builder,iterator);
		}
		builder.insert(0,'(');
		builder.append(')');
		return builder;
	}
	
	private void addArgument(StringBuilder builder,
			TokenChain<? extends Token> iterator) {
		if(builder.length()>0){
			builder.insert(0, ',');
			iterator.previous();
		}
		builder.insert(0, extractType(iterator));
	}
	
	private void addMethod(TokenChain<? extends Token> iterator) {
		skipThrowsSection(iterator);
		StringBuilder args = getArguments(iterator);
		if(methods.get(classes.getLast())==null){
			methods.put(classes.getLast(), new LinkedList<String>());
		}
		args.insert(0,iterator.previous().getToken());
		String signature = args.toString();
		args.insert(0,' ');
		args.insert(0,extractType(iterator));
		methods.get(classes.getLast()).add(args.toString());
		resolveModifiers(iterator,signature,TYPE_METHOD);
	}
	
	private void addImplemenedInterfaces(TokenChain<? extends Token> iterator) {
		List<String> result = new LinkedList<String>();
		iterator.next();
		while(!iterator.current().getToken().equals(IMPLEMENTS_KEYWORD)){
			result.add(extractType(iterator));
			iterator.previous();
		}
		Collections.reverse(result);
		implementedInterfaces.put(classes.getLast(), result);
	}
	
	private void addExtendedClass(TokenChain<? extends Token> iterator) {
		iterator.next();
		superClasses.put(classes.getLast(), extractType(iterator));
	}
	
	private void addImportDeclaration(TokenChain<? extends Token> iterator) {
		StringBuilder value = new StringBuilder();
		while(!iterator.previous().getToken().equals(IMPORT_KEYWORD));
		while(!iterator.next().getToken().equals(";")){
			value.append(iterator.current().getToken());
		}
		String cls = value.toString();
		importsCache.put(Name.getSimpleName(cls), cls);
	}
}
